﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Net;
using CookieCollection=Business.Entities.CookieCollection;
using System.IO;
using Business.Entities;
using System.Text;
using Business.Entities.Actions;

namespace Business.Logic
{
    /// <summary>
    /// Api for accessing google tasks
    /// </summary>
    public class GoogleApi
    {
        private const int MaxActionsPerTransaction = 15;
        public static bool AllowWriteStreamBuffering;
        private static int Timeout = 20000; // ms
        public static bool UseProxy = false;
        public static string ProxyHost = "192.168.1.34";
        public static int ProxyPort = 8888;

        private LoginErrorPackage _errorPackage;
        /// <summary>
        /// Debug data
        /// </summary>
        public LoginErrorPackage ErrorPackage
        {
            get { return _errorPackage; }
        }

        /// <summary>
        /// Indended for trial version
        /// </summary>
        public bool DisableMultipleLists = false;
        /// <summary>
        /// Only relevant if DisableMultipleLists == true
        /// </summary>
        public string SingleListName = null;

        /// <summary>
        /// Set to true for actions to be queued and executed transactionwise.
        /// </summary>
        public bool Transactional;
        private CookieCollection _cookies;
        public static long LatestSyncPoint;
        private IJsonApi _jsonApi;
        private RequestBuilder _requestBuilder;
        private GoogleRequest _transaction;
        public IProgressIndicator ProgressIndicator;
        private bool _isGoogleApps = false;
        private string _googleAppsDomain;

        private static string _mailGoogleComHost;
        /// <summary>
        /// The MailGoogleComUrl without the protocol prefix
        /// </summary>
        public static string MailGoogleComHost
        {
            get { return _mailGoogleComHost; }
        }
        /// <summary>
        /// Change this in order to proxy through another host
        /// </summary>
        private static string _mailGoogleComUrl;
        public static string MailGoogleComUrl
        {
            get { return _mailGoogleComUrl; }
            set 
            {
                _mailGoogleComUrl = value;

                // Are reverting to HTTP  (not HTTPS)?
                DoHttp = false;
                if (_mailGoogleComUrl.Contains("http://")) DoHttp = true;

                // Set host part (is protocol included in url?)
                int hostIx = _mailGoogleComUrl.IndexOf("://") + 3;
                if (hostIx > 2) _mailGoogleComHost = _mailGoogleComUrl.Substring(hostIx);
                else _mailGoogleComHost = _mailGoogleComUrl;
            }
        }

        

        /// <summary>
        /// If false, do https
        /// </summary>
        public static bool DoHttp;
        public const string MailGoogleComHostDefault = "mail.google.com";

        /// <summary>
        /// Protocol URL prefix
        /// </summary>
        public static string Protocol
        {
            get 
            {
                return DoHttp ? "http" : "https";
            }

        }
        private const string GoogleTasksUrl     = "{0}://{1}/tasks/ig";
        private const string GoogleTasksPostUrl = "{0}://{1}/tasks/r/ig";
        // From dec 2010, Apps uses same URL as standard tasks. 22.Jan2010:: FOR SOME USERS....Rewrote again
        //private const string AppsTasksUrl       = "{0}://{1}/tasks/ig";
        //private const string AppsTasksPostUrl   = "{0}://{1}/tasks/r/ig";  
        private const string AppsTasksUrl = "{0}://{1}/tasks/a/{2}/ig";
        private const string AppsTasksPostUrl = "{0}://{1}/tasks/a/{2}/r/ig";

        public GoogleRequest Transaction
        {
            get { return _transaction; }
        }

        static GoogleApi()
        {
            MailGoogleComUrl = MailGoogleComHostDefault;
        }

        /// <summary>
        /// Consturctor
        /// </summary>
        /// <param name="cookies">List of cookies that authenticates this session</param>
        /// <param name="jsonApi">Json api module</param>
        /// <param name="googleAppsDomain">Google Apps domain or null if not</param>
        public GoogleApi(IJsonApi jsonApi, CookieCollection cookies, string googleAppsDomain)
        {
            _jsonApi = jsonApi;
            _cookies = cookies;
            _isGoogleApps = googleAppsDomain != null;
            _googleAppsDomain = googleAppsDomain;
            _requestBuilder = new RequestBuilder();
            _transaction = _requestBuilder.CreateRequest();
        }

        public void Test()
        {
            
        }
        #region Business methods

        /// <summary>
        /// Prepares a HTTP GET WebRequest to the speicifed URL
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        internal static HttpWebRequest GetGetRequest(string url, WebHeaderCollection additionalHeaders)
        {
            if (!url.Contains("SetSID")) url = url.Replace("http:", "https:");
            var rq = (HttpWebRequest)WebRequest.Create(url);
            if (UseProxy) rq.Proxy = new WebProxy(ProxyHost, ProxyPort);
            rq.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
            rq.AllowAutoRedirect = false;
            rq.Timeout = Timeout;
            rq.Method = "GET";
            if (additionalHeaders != null)
            {
                foreach (string key in additionalHeaders.Keys) rq.Headers.Add(key, additionalHeaders[key]);
            }
            return rq;
        }
        
        /// <summary>
        /// Creates and opens a post request, writing post data.
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postData"></param>
        /// <param name="additionalHeaders">Optional list of additional headers</param>
        /// <returns></returns>
        internal static HttpWebRequest GetPostReqest(string url, string postData, WebHeaderCollection additionalHeaders)
        {
            url = url.Replace("http:", "https:");
            var rq = (HttpWebRequest)WebRequest.Create(url);
            if (UseProxy) rq.Proxy = new WebProxy(ProxyHost, ProxyPort);
            rq.Timeout = Timeout;
            rq.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
            rq.AllowAutoRedirect = false;
            rq.Method = "POST";
            rq.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
            if (additionalHeaders != null)
            {
                foreach (string key in additionalHeaders.Keys) rq.Headers.Add(key, additionalHeaders[key]);
            }
            byte[] bytes = Encoding.UTF8.GetBytes(postData);
            rq.ContentLength = bytes.Length;
            using (Stream requestStream = rq.GetRequestStream())
            {
                // NOTE: KAN VÆRE HER HVIS BYTES.LEN = 0
                if (bytes.Length == 0) throw new ArgumentException("Attempt to HTTP-post zero bytes. URL:"+ url); 
                requestStream.Write(bytes, 0, bytes.Length);
                requestStream.Close();
            }
            return rq;
        }

        /// <summary>
        /// Performs a HTTP Get call to the Google tasks web and returns the given data
        /// </summary>
        private string HttpGet(string url)
        {
            var whc = new WebHeaderCollection();
            whc.Set("Cookie", _cookies.ToStringFor(url));
            var rq = GetGetRequest(url, whc);

            using (var response = (HttpWebResponse)rq.GetResponse())
            {
                _cookies.SetCookiesFiltered(response); // contains GTL
                using (var stream = response.GetResponseStream())
                {
                    var reader = new StreamReader(stream);
                    var s = reader.ReadToEnd();
                    return s;
                }
            }
        }

        // at=1, cookeis

        /// Performs a HTTP Post call to the Google tasks web and returns the given data
        /// </summary>
        /// <param name="postData">String to post.</param>
        private string HttpPost(string postData)
        {
            string url = GetGooglePostUrl();
            var whc = new WebHeaderCollection();
            whc.Set("Cookie", _cookies.ToStringFor(url));
            whc.Set("AT", "1"); // Don't know what this is but google requires it)
            var rq = GetPostReqest(url, postData, whc);
            using (WebResponse response = rq.GetResponse())
            {
                using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                {
                    return reader.ReadToEnd();
                }
            }
        }



        ///// <summary>
        ///// Performs a HTTP Post call to the Google tasks web and returns the given data
        ///// </summary>
        ///// <param name="postData">String to post.</param>
        //private string HttpPost_(string postData)
        //{
        //    var rq = (HttpWebRequest)WebRequest.Create(GoogleTasksPostUrl);
        //    rq.Timeout = Timeout;
        //    rq.AllowWriteStreamBuffering = AllowWriteStreamBuffering;
        //    rq.AllowAutoRedirect = false;
        //    rq.Method = "POST";
        //    rq.Headers.Set("Cookie", _cookies.ToString());
        //    rq.Headers.Set("AT", "1"); // Don't know what this is but google requires it
        //    rq.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
        //    byte[] bytes = Encoding.UTF8.GetBytes(postData);
        //    rq.ContentLength = bytes.Length;
        //    //var postStream = new StreamWriter(rq.GetRequestStream(), Encoding.UTF8);
        //    //postStream.Write(bytes);
        //    //postStream.Close();
        //    using (Stream requestStream = rq.GetRequestStream())
        //    {
        //        requestStream.Write(bytes, 0, bytes.Length);
        //        requestStream.Close();

        //        using (WebResponse response = rq.GetResponse())
        //        {
        //            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        //            {
        //                return reader.ReadToEnd();
        //            }
        //        }
        //    }

        //    // Perform POST, read cookies and return
        //    //var response = rq.GetResponse();
        //    //_cookies.SetCookiesFiltered(response.Headers); // contains GTL
        //    //var stream = response.GetResponseStream();
        //    //var reader = new StreamReader(stream);
        //    //var s = reader.ReadToEnd();
        //    //reader.Close();
        //    //return s;
        //}

        /// <summary>
        /// Wraps single action in a request and executes it
        /// </summary>
        /// <param name="action"></param>
        /// <returns></returns>
        private TasksRoot PerformRequest(GoogleAction action)
        {
            var request = _requestBuilder.CreateRequest();
            action.action_id = "1"; 
            request.action_list.Add(action);
            return PerformRequest(request);
        }

        /// <summary>
        /// Serializes the request and posts it to google and parses and desirialize return data
        /// </summary>
        /// <param name="request"></param>
        private TasksRoot PerformRequest(GoogleRequest request)
        {
            string jsonRequest = "r=" + StringUtil.GoogleEncode(_jsonApi.Serialize(request));
            string jsonResponse = HttpPost(jsonRequest);
            var tasksRoot = _jsonApi.Deserialize<TasksRoot>(jsonResponse);
            LatestSyncPoint = tasksRoot.latest_sync_point;
            
            if (tasksRoot.results != null)
            {
                // Invoke events for result listeners
                foreach (Result result in tasksRoot.results)
                {
                    var action = request.GetAction(result.action_id);
                    if (action != null) action.ResultRetrieved(result);
                }
                // Throw general exception on error
                foreach (Result result in tasksRoot.results)
                {
                    var action = request.GetAction(result.action_id);
                    if (result.result_type.ToLower() == "error")
                    {
                        if (result.error_code == 3 && action != null && action.IsDeleteListAction)
                        {
                            // Ignore. This means we tried to delete the first list we created. Google disallows.
                        }
                        else throw new GoogleException(result);
                    }
                }
            }
            return tasksRoot;
        }

        /// <summary>
        /// Executes any queued actions
        /// </summary>
        /// <returns></returns>
        public void CommitTransaction()
        {
            if (_transaction == null || _transaction.action_list.Count == 0) return;
            
            // Split transaction in chunks
            int totCount = _transaction.action_list.Count;
            for (int transIndex = 0; transIndex < totCount; )
            {
                GoogleRequest transChunk = _requestBuilder.CreateRequest();
                for (int i = 0; i < MaxActionsPerTransaction && transIndex < totCount; i++)
                {
                    transChunk.AddAction(_transaction.action_list[transIndex]);
                    transIndex++;
                }
                if (ProgressIndicator != null) ProgressIndicator.NextMinorStep();
                // Perform request
                Trace.WriteLine(" -> Committing transaction (" + transIndex + "/" + totCount + ")");
                PerformRequest(transChunk);
            }

            //Trace.WriteLine(" ->Committing transaction (" + _transaction.action_list.Count +")");
            //PerformRequest(_transaction);
            _transaction = _requestBuilder.CreateRequest(); // Prepare next
        }

        #endregion

        #region standard calls

        private string GetGoogleGetUrl()
        {
            string url = String.Format(GoogleTasksUrl, Protocol, MailGoogleComHost);
            if (_isGoogleApps) url = String.Format(AppsTasksUrl, Protocol, MailGoogleComHost, _googleAppsDomain);
            //Trace.WriteLine("GET-URL [" + url + "]");
            return url;
        }

        private string GetGooglePostUrl()
        {
            string url = String.Format(GoogleTasksPostUrl, Protocol, MailGoogleComHost);
            if (_isGoogleApps) url = String.Format(AppsTasksPostUrl, Protocol, MailGoogleComHost, _googleAppsDomain);
            //Trace.WriteLine("POSTURL ["+url+"]");
            return url;
        }

        /// <summary>
        /// Non-transaction-call: Performs a call to the index page with the list of task list and the current task list
        /// </summary>
        /// <returns></returns>
        public GoogleTasksIndex GetIndexPage()
        {
            string url = GetGoogleGetUrl();
            string html = HttpGet(url);
            
            // Grab everything between these two strings
            string start = "function _init(){_setup(";
            string end = ")}</script>";
            int iStart = html.IndexOf(start);
            int iEnd = html.IndexOf(end);
            if (iStart == -1 || iEnd == -1 || iEnd < iStart) 
            {
                var httpLog = new HttpLog(url, -2);
                httpLog.SetHtml(html);
                _errorPackage = new LoginErrorPackage();
                _errorPackage.AddHttpLog(httpLog);
                throw new DataException("Fetchin index page failed. Try reauthenticating from the settings menu by clicking authenticate and entering your password again.");
            }
            iStart += start.Length;
            string json = html.Substring(iStart, iEnd - iStart);
            // Parse
            var index = _jsonApi.Deserialize<GoogleTasksIndex>(json);
            LatestSyncPoint = index.t.latest_sync_point;
            return index;
        }
        /// <summary>
        /// Fetches all task lists with content
        /// </summary>
        /// <returns></returns>
        public TasksRoot GetAllLists()
        {
            return GetAllLists(false);
        }

        /// <summary>
        /// Fetches all task lists with content
        /// </summary>
        /// <param name="forceGetAll">If true, ignoresthe DisableMultipleLists param</param>
        /// <returns></returns>
        public TasksRoot GetAllLists(bool forceGetAll)
        {
            Trace.WriteLine("Getting all lists...");
            var root = GetIndexPage();
            if (ProgressIndicator != null) ProgressIndicator.NextMajorStep();
            GetAllLists(root.t, forceGetAll);
            root.t.PostProcessLoad();
            Trace.WriteLine("Get all lists done.");
            return root.t;
        }

        private void GetAllLists(TasksRoot root, bool forceGetAll)
        {
            var before = Transactional;
            Transactional = false;
            List<TaskList> newLists = new List<TaskList>();
            List<TaskList> removeList = new List<TaskList>();
            foreach (TaskList list in root.lists)
            {
                Trace.WriteLine("  Getting list ["+list.id+"]");
                var newList = GetList(list.GoogleId);
                newLists.Add(newList);
                if (!forceGetAll && DisableMultipleLists && SingleListName != newList.name && SingleListName != null) removeList.Add(newList);
                if (ProgressIndicator != null) ProgressIndicator.NextMinorStep();
            }
            foreach (var list in newLists) root.Replace(list);
            foreach (var list in removeList) root.lists.Remove(list);
            Transactional = before;
        }

        public TaskList GetList(string id)
        {
            var action = _requestBuilder.GetList(id);
            TasksRoot tasksRoot = PerformRequest(action);
            tasksRoot.AddTasksToList();
            return tasksRoot.GetList(id);
        }

        //public TaskList GetListAsync(string id)
        //{
        //    var action = _requestBuilder.GetList(id);
        //    action.ResultReceived += delegate(Result result)
        //                                 {
        //                                     int a = 1;
        //                                 };
        //    QueueOrPerform(action);
        //    //tasksRoot.AddTasksToList();
        //    //return tasksRoot.GetList(id);
        //    return new TaskList();
        //}

        #endregion

        #region Transactional calls
        /// <summary>
        /// Creates a task in the specified parent list
        /// </summary>
        public void CreateTask(TaskList parent, Task task, ResultEventHandler resultHandler)
        {
            var action = _requestBuilder.CreateTask(parent, task);
            action.ResultReceived += delegate(Result result)
            {
                task.SetNewId(result.new_id);
                // TEMP : trace to remove
                Trace.WriteLine("  New task id set: [" + result.new_id + "] for " + task);
            };
            if (resultHandler != null) action.ResultReceived += resultHandler;
            QueueOrPerform(action);
        }

        /// <summary>
        /// Creates a task in the specified parent list
        /// </summary>
        public void CreateTask(TaskList parent, Task task)
        {
            CreateTask(parent, task, null);
        }

        /// <summary>
        /// Queues or executes the action immediately, depending on configuration
        /// </summary>
        /// <param name="action"></param>
        private void QueueOrPerform(GoogleAction action)
        {
            if (Transactional) _transaction.AddAction(action);
            else PerformRequest(action);
        }

        public void UpdateTask(Task task)
        {
            var action = _requestBuilder.UpdateTask(task);
            QueueOrPerform(action);
        }

        public void DeleteTask(Task task)
        {
            var action = _requestBuilder.DeleteTask(task.GoogleId);
            QueueOrPerform(action);
        }


        internal void MoveTask(Task gTask, TaskList sourceList, TaskList destList)
        {

            var action = _requestBuilder.MoveTask(gTask, sourceList.GoogleId, destList.GoogleId, destList.GoogleId);
            QueueOrPerform(action);
            //if (result.results.Count > 0)
            //    throw new GoogleException(String.Format("Unable to move task {0} from group {1} to group {2}", gTask.id, sourceList.id, destList.id));
            //sourceList.child_entity.Remove(gTask);
            //destList.child_entity.Add(gTask);
        }

        public void CreateList(TaskList list)
        {
            var listName = list.name;
            if (String.IsNullOrEmpty(listName)) listName = TaskList.DefaultListName;
            var action = _requestBuilder.CreateList(listName);
            action.ResultReceived += delegate(Result result)
                {
                    if (result.result_type != "new_entity") throw new GoogleException(result);
                    list.SetNewId(result.new_id);
                };
            QueueOrPerform(action);
        }

        public void DeleteList(TaskList list)
        {
            var action = _requestBuilder.DeleteList(list.GoogleId);
            QueueOrPerform(action);
        }

        public void RenameList(TaskList list, string newName)
        {
            var action = _requestBuilder.RenameList(list.GoogleId, newName);
            QueueOrPerform(action);
            //list.name = newName;
        }

        public void UpdateList(TaskList list)
        {
            var action = _requestBuilder.UpdateList(list);
            QueueOrPerform(action);
        }

        /// <summary>
        /// Moves task internally within a list. Used for indentation or reordering
        /// </summary>
        public void MoveTaskInternally(Task taskToMove, Task priorTask)
        {
            var action = _requestBuilder.MoveTaskAfter(taskToMove, priorTask, taskToMove.ParentElementId);
            QueueOrPerform(action);
        }

        ///// <summary>
        ///// Moves task internally within a list. Used for indentation changes
        ///// </summary>
        ///// <param name="gTask">Task to move</param>
        ///// <param name="destParent">ParentList task or list if no indentation</param>
        //internal void MoveTaskInternally(Task gTask, string destParent)
        //{
        //    var action = _requestBuilder.MoveTask(gTask, gTask.ListId, null, destParent);
        //    QueueOrPerform(action);
        //}
        #endregion

    }
}
